#pragma once
#include "object_pool.h"
#include <assert.h>
#include <iostream>



template<class Obj, class Mutex /*= std::mutex*/>
void ObjectPool<Obj, Mutex>::DebugInfo()
{
	// static char msg[128] = { 0 };
	// ZERO_MEMORY(msg, 128);
	// sprintf_s(msg, "\nobj = %s \nfree_obj_count = %zu, total_obj_count = %zu \nuse_obj_count = %zu, used_obj_map_ = %zu \n\n", 
	// 	name_.c_str(), 
	// 	free_obj_count_, total_obj_count_,
	// 	use_obj_count_, use_obj_map_.size()
	// );
	// LOG_INFO << msg;
}

template<class Obj, class Mutex /*= std::mutex*/>
ObjectPool<Obj, Mutex>::ObjectPool(int count /*= E_DEFAULT::DEFAULT_POOL_OBJ_COUNT*/)
{
	name_.clear();
	obj_vec_.clear();
	obj_vec_.clear();

	use_obj_count_ = 0;
	free_obj_count_ = 0;
	total_obj_count_ = 0;

	reserve(count);
	name_ = typeid(Obj).name();
}

template<class Obj, class Mutex /*= std::mutex*/>
ObjectPool<Obj, Mutex>::~ObjectPool()
{
	clear();
}

template<class Obj, class Mutex /*= std::mutex*/>
size_t ObjectPool<Obj, Mutex>::TotalObjCount() const
{
	return total_obj_count_;
}

template<class Obj, class Mutex /*= std::mutex*/>
size_t ObjectPool<Obj, Mutex>::FreeObjCount() const
{
	return free_obj_count_;
}

template<class Obj, class Mutex /*= std::mutex*/>
size_t ObjectPool<Obj, Mutex>::UseObjCount() const
{
	return use_obj_count_;
}

template<class Obj, class Mutex /*= std::mutex*/>
void ObjectPool<Obj, Mutex>::reserve(int count)
{
	std::lock_guard<Mutex> lock(mutex_);
	obj_vec_.reserve(count);
}

template<class Obj, class Mutex /*= std::mutex*/>
bool ObjectPool<Obj, Mutex>::empty() const
{
	std::lock_guard<Mutex> lock(mutex_);
	return obj_vec_.empty();
}

template<class Obj, class Mutex /*= std::mutex*/>
void ObjectPool<Obj, Mutex>::resize(int initCount)
{
	std::lock_guard<Mutex> lock(mutex_);
	initCount = initCount > 0 ? initCount : E_DEFAULT::DEFAULT_POOL_OBJ_COUNT;
	for (int i = 0; i < initCount; ++i)
	{
		ObjectPlus *p = create();
		obj_vec_.emplace_back(p);
		use_obj_map_[(uint64)(p->obj)] = p;
		free_obj_count_++;
		total_obj_count_++;
	}
}

template<class Obj, class Mutex /*= std::mutex*/>
void ObjectPool<Obj, Mutex>::clear()
{
	std::lock_guard<Mutex> lock(mutex_);
	ObjectPlus *obj = nullptr;
	while (obj_vec_.empty() == false)
	{
		obj = obj_vec_.back();
		obj_vec_.pop_back();
		auto iter = use_obj_map_.find((uint64)(obj->obj));
		if (iter != use_obj_map_.end())
			use_obj_map_.erase(iter);

		delete obj->obj;
		obj->obj = nullptr;
		delete obj;
		obj = nullptr;
	}

	use_obj_count_ = 0;
	free_obj_count_ = 0;
	total_obj_count_ = 0;
	ObjMap().swap(use_obj_map_);
	ObjVec().swap(obj_vec_);
}

template<class Obj, class Mutex>
void ObjectPool<Obj, Mutex>::push(Obj *obj)
{
	std::lock_guard<Mutex> lock(mutex_);
	obj->reset();

	uint64 addr = (uint64)(obj);
	auto iter = use_obj_map_.find(addr);
	if (iter == use_obj_map_.end())
	{
		ObjectPlus *p = create(obj);
		p->is_use = false;
		use_obj_map_[addr] = p;
		obj_vec_.push_back(p);
		total_obj_count_++;
	}
	else
	{
		iter->second->is_use = false;
		obj_vec_.push_back(iter->second);
	}
	free_obj_count_++;
	use_obj_count_--;
}

template<class Obj, class Mutex>
Obj *ObjectPool<Obj, Mutex>::pop()
{
	std::lock_guard<Mutex> lock(mutex_);

	ObjectPlus *tmp = nullptr;
	if (obj_vec_.empty())
	{
		tmp = create();
		uint64 addr = (uint64)tmp->obj;
		use_obj_map_[addr] = tmp;
		total_obj_count_++;
	}
	else
	{
		tmp = obj_vec_.back();
		obj_vec_.pop_back();
		free_obj_count_--;
	}

	tmp->is_use = true;
	use_obj_count_++;

	assert(use_obj_count_ + free_obj_count_ == total_obj_count_);
	return tmp->obj;
}

const uint32_t MIN_BLOCK_ALIGN = 64;

void *aligned_malloc(size_t size, size_t align);
void aligned_free(void *ptr);

// Aligns n to align. N will be unchanged if it is already aligned
inline size_t align_to(size_t n, size_t align)
{
	return (1 + (n - 1) / align) * align;
}

template<class T, class Mutex>
ObjectPoolBlock<T, Mutex> *ObjectPoolBlock<T, Mutex>::create(uint32 entries_per_block)
{
	// the header size
	const size_t header_size = sizeof(ObjectPoolBlock<T, Mutex>);
#if _MSC_VER <= 1800
	const size_t entry_align = __alignof(T);
#else
	const size_t entry_align = alignof(T);
#endif
	// extend indices size by alignment of T
	// const size_t indices_size = align_to(sizeof(index_t) * entries_per_block, entry_align);
	const size_t indices_size = align_to(sizeof(uint32) * entries_per_block, sizeof(uint32));
	// align block to cache line size, or entry alignment if larger
	const size_t entries_size = sizeof(T) * entries_per_block;
	// block size includes indices + entry alignment + entries
	const size_t block_size = header_size + indices_size + entries_size;
	ObjectPoolBlock<T, Mutex> *ptr =
		reinterpret_cast<ObjectPoolBlock<T, Mutex> *>(aligned_malloc(block_size, MIN_BLOCK_ALIGN));
	if (ptr)
	{
		new (ptr) ObjectPoolBlock(entries_per_block);
		assert(reinterpret_cast<uint8_t *>(ptr->indices_begin())
			== reinterpret_cast<uint8_t *>(ptr) + header_size);
		assert(reinterpret_cast<uint8_t *>(ptr->memory_begin())
			== reinterpret_cast<uint8_t *>(ptr) + header_size + indices_size);
	}
	return ptr;
}

template <class T, class Mutex>
void ObjectPoolBlock<T, Mutex>::destroy(ObjectPoolBlock<T, Mutex> *ptr)
{
	ptr->~ObjectPoolBlock();
	aligned_free(ptr);
}

template <class T, class Mutex>
ObjectPoolBlock<T, Mutex>::ObjectPoolBlock(uint32 entries_per_block)
	: free_head_index_(0), entries_per_block_(entries_per_block)
{
	uint32 *indices = indices_begin();
	for (uint32 i = 0; i < entries_per_block; ++i)
	{
		indices[i] = i + 1;
	}
}

template <class T, class Mutex>
void destruct_all(ObjectPoolBlock<T, Mutex> &,
	typename std::enable_if<std::is_trivially_destructible<T>::value>::type * = 0)
{
	// skip calling destructors for trivially destructible types
}

template <class T, class Mutex>
void destruct_all(ObjectPoolBlock<T, Mutex> &t,
	typename std::enable_if<!std::is_trivially_destructible<T>::value>::type * = 0)
{
	// call destructors on all live objects in the pool
	t.for_each([](T *ptr)
		{
			ptr->~T();
		});
}

template <class T, class Mutex>
ObjectPoolBlock<T, Mutex>::~ObjectPoolBlock()
{
	// destruct any allocated objects
	destruct_all(*this);
	//AS_INFO_LOG << "delete object" << std::endl;
}

template <class T, class Mutex>
uint32 *ObjectPoolBlock<T, Mutex>::indices_begin() const
{
	// calculcates the start of the indicies
	return reinterpret_cast<uint32 *>(const_cast<ObjectPoolBlock<T, Mutex>*>(this + 1));
}

template <class T, class Mutex>
T *ObjectPoolBlock<T, Mutex>::memory_begin() const
{
	// calculates the start of pool memory
	return reinterpret_cast<T *>(indices_begin() + entries_per_block_);
}

template <class T, class Mutex>
const T *ObjectPoolBlock<T, Mutex>::memory_offset() const
{
	return memory_begin();
}

template <class T, class Mutex>
template <class... P>
T *ObjectPoolBlock<T, Mutex>::pop(P&&... params)
{
	// get the head of the free list
	const uint32 index = free_head_index_;
	if (index != entries_per_block_)
	{
		uint32 *indices = indices_begin();
		// assert that this index is not in use
		assert(indices[index] != index);
		// update head of the free list
		free_head_index_ = indices[index];
		// flag index as used by assigning it's own index
		indices[index] = index;
		// get object memory
		T *ptr = memory_begin() + index;
		// construct the entry
		new (ptr) T(std::forward<P>(params)...);
		return ptr;
	}
	return nullptr;
}

template <class T, class Mutex>
void ObjectPoolBlock<T, Mutex>::push(const T *ptr)
{
	if (ptr)
	{
		// assert that pointer is in range
		const T *begin = memory_begin();
		assert(ptr >= begin && ptr < (begin + entries_per_block_));
		// destruct this object
		ptr->~T();
		// get the index of this pointer
		const uint32 index = static_cast<uint32>(ptr - begin);
		uint32 *indices = indices_begin();
		// assert this index is allocated
		assert(indices[index] == index);
		// remove index from used list
		indices[index] = free_head_index_;
		// store index of next free entry in this entry
		free_head_index_ = index;
	}
}

template <class T, class Mutex>
template <typename F>
void ObjectPoolBlock<T, Mutex>::for_each(const F func) const
{
	const uint32 *indices = indices_begin();
	T *first = memory_begin();
	for (uint32 i = 0, count = entries_per_block_; i != count; ++i)
	{
		if (indices[i] == i)
		{
			func(first + i);
		}
	}
}

template <class T, class Mutex>
void ObjectPoolBlock<T, Mutex>::delete_all()
{
	// destruct any allocated objects
	destruct_all(*this);
	free_head_index_ = 0;
	uint32 *indices = indices_begin();
	for (uint32 i = 0; i < entries_per_block_; ++i)
	{
		indices[i] = i + 1;
	}
}

template <class T, class Mutex>
uint32 ObjectPoolBlock<T, Mutex>::num_allocations() const
{
	uint32 num_allocs = 0;
	for_each([&num_allocs](const T *)
		{
			++num_allocs;
		});
	return num_allocs;
}
